package com.wanrue.tcc.core;

import com.alibaba.fastjson.JSON;
import com.wanrue.tcc.commom.TransactionManager;
import com.wanrue.tcc.common.ContextService;
import com.wanrue.tcc.common.TccMethod;
import com.wanrue.tcc.context.*;
import com.wanrue.tcc.enums.Role;
import com.wanrue.tcc.enums.Status;
import com.wanrue.tcc.exception.SystemException;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;

import javax.annotation.Resource;
import java.util.Set;

/**
 * tcc方法的拦截与处理
 * Created by wenjie on 10/15/18.
 */
@Setter
@Log4j
public class TransactionInterceptor {

    @Resource
    private ContextService contextService;

    /**
     * 事务管理器
     */
    private TransactionManager transactionManager;

    /**
     * 异常类型(判断是否是超时异常)
     */
    private Set<Class<? extends Exception>> delayCancelExceptions;

    public Object interceptTccMethod(ProceedingJoinPoint pjp) throws Throwable {
        TccMethod tccMethod = contextService.method(pjp);
        boolean isTransactionActive = transactionManager.isTransactionActive();
        // 只要不是 MANDATORY 就不会抛异常
        if (!tccMethod.isLegalContext(isTransactionActive)) {
            throw new SystemException("no active tcc transaction while propagation is mandatory for method " + tccMethod.getMethod().getName());
        }
        //只要是 REQUIRES_NEW 总会返回 INITIATOR
        Role action = tccMethod.role(isTransactionActive);
        if (action == Role.INITIATOR) {//发起事务
            return initiatorProceed(tccMethod);
        } else if (action == Role.PARTICIPANT) {//参与分支事务
            return participantProceed(tccMethod);
        } else if (action == Role.NORMAL) {//处理 Propagation为SUPPORTS的情况
            normalProceed(tccMethod);
            return tccMethod.proceed();
        } else {//不处理
            return pjp.proceed();
        }
    }

    //处理事务发起者的业务
    private Object initiatorProceed(TccMethod tccMethod) throws Throwable {
        Object proceed;
        Transaction transaction = transactionManager.begin(tccMethod);// 开启事务
        Xid xid = transaction.getXid();
        contextService.set(new Context(xid, Status.TRYING), tccMethod.getMethod(), tccMethod.getArgs());//调用方法前设置上下文
        try {
            try {
                proceed = tccMethod.proceed();
            } catch (Throwable tryingException) {
                if (!isDelayCancelException(tryingException)) {
                    log.warn(String.format("tcc transaction trying failed. transaction content:%s", JSON.toJSONString(transaction)), tryingException);
                    transactionManager.rollback(tccMethod.isAsyncCancel());
                }
                throw tryingException.getCause();
            }
            transactionManager.commit(tccMethod.isAsyncConfirm());
        } finally {
            transactionManager.clean(transaction);
        }
        return proceed;
    }

    // 处理参与者的 try confirm cancel 三种状态
    private Object participantProceed(TccMethod tccMethod) throws Throwable {
        Object result = tccMethod.getValue();
        Transaction transaction = null;
        Context context = tccMethod.getContext();
        if (context == null) {
            throw new SystemException("the context is null for method " + tccMethod.getMethod().getName());
        }
        Status Status = context.getStatus();
        try {
            if (Status == Status.TRYING) {
                transaction = transactionManager.beginBranch(context, tccMethod);
                Xid xid = transaction.getXid();//分支事务的 xid
                contextService.set(new Context(xid, Status.TRYING), tccMethod.getMethod(), tccMethod.getArgs());//调用方法前设置上下文
                return tccMethod.proceed();
            } else if (Status == Status.CONFIRMING) {
                transaction = transactionManager.getBranch(context);
                transactionManager.commit(tccMethod.isAsyncConfirm());
            } else if (Status == Status.CANCELLING) {
                transaction = transactionManager.getBranch(context);
                transactionManager.rollback(tccMethod.isAsyncCancel());
            }
        } catch (Exception e) {
            throw e.getCause();
        } finally {
            transactionManager.clean(transaction);
        }
        return result;
    }

    // rpc获取调用链路
    public void normalProceed(TccMethod tccMethod) {
        Transaction transaction = transactionManager.getCurrentTransaction();
        if (transaction != null && transaction.getStatus() == Status.TRYING) {//只有try的时候才获取rpc调用链路
            Xid xid = new Xid(transaction.getXid().getGlobalTransactionId());
            contextService.set(new Context(xid, Status.TRYING), tccMethod.getMethod(), tccMethod.getArgs());//调用方法前设置上下文
            Invocation invocation = new Invocation(tccMethod.getTargetClass(), tccMethod.getMethodName(), tccMethod.getParameterTypes(), tccMethod.getArgs());
            Participant participant = new Participant(xid, invocation, invocation);
            transactionManager.addParticipant(participant);
        }
    }

    private boolean isDelayCancelException(Throwable throwable) {
        if (delayCancelExceptions != null) {
            for (Class delayCancelException : delayCancelExceptions) {
                Throwable rootCause = ExceptionUtils.getRootCause(throwable);
                if (delayCancelException.isAssignableFrom(throwable.getClass()) || (rootCause != null && delayCancelException.isAssignableFrom(rootCause.getClass()))) {
                    return true;
                }
            }
        }
        return false;
    }
}
